package net.asrsmid.invoker.as;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.reflect.Array;
import java.text.DecimalFormatSymbols;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;

import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.Font;
import org.apache.poi.ss.usermodel.IndexedColors;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.util.CellRangeAddress; 
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;

/**
 * public class TableUtility <br><br>
 * TableUtility is a utility class providing a set of methods to manipulate a {@link Table} object
 * @author Chen WANG
 * @since 20 June 2011
 */
public class TableUtility {
	/**
	 * Returns a string which is the alias of the given axis number. The alias will be COLUMNS,
	 * ROWS, PAGES, SECTIONS, CHAPTERS if the given axis number is inferior to 5, otherwise
	 * a common form like "AXIS(a)" will be returned.
	 * @param a the axis number
	 * @return a string as the alias of the given axis number
	 */
	public static String getAxisAlias(int a) {
		String s = null;
		switch (a) {
		case 0 : s = "COLUMNS"; break;
		case 1 : s = "ROWS"; break;
		case 2 : s = "PAGES"; break;
		case 3 : s = "SECTIONS"; break;
		case 4 : s = "CHAPTERS"; break;
		default : s = "AXIS(" + Integer.toString(a) + ")"; break;
		}
		return s;
	}
	
	/**
	 * Returns all sub table of 2 dimension of the current table 
	 * @param t parent table at the top level
	 * @return an array of Table containing all sub tables of 2dimension
	 * @throws TableException
	 */
	public static Table[] getAllSubTables(Table t) throws TableException {
		Table[] tabs = null;

		// initialize an array of 2-dim table
		int[] tDims = t.getDimensions();
		int tDim = tDims.length;
		
		if (tDim < 3) {
			// return itself in an array if is already a table of 2 dimensions
			tabs = new Table[] {t};
			return tabs;
		} else {
			// find the number of all 2-dim tables
			int[][] allCoords = null;
			int[] coordsDims = new int[tDim - 2];
			int coordsNum = 1;
			for (int i = 0; i < tDim - 2; i++) {
				coordsNum = coordsNum * tDims[i];
				coordsDims[i] = tDims[i];
			}
			allCoords = new int[coordsNum][coordsDims.length];
			
			// calculate all possible 2-dim sub tables' coordinates (in top level parent table)
			for (int i = 0; i < coordsDims.length; i++) {
				int dimWidth = coordsDims[coordsDims.length - i - 1];
				int repeat = 1;
				for (int j = coordsDims.length - i - 1; j < coordsDims.length; j++) {
					if (j + 1 < coordsDims.length)
						repeat = repeat * coordsDims[j + 1];
				}
				List<Integer> dimCoordList = new ArrayList<Integer>();
				for (int n = 0; n < dimWidth; n++) {
					dimCoordList.add(n);
				}
				CircleLinkedList<Integer> circleDimCoordList = new CircleLinkedList<Integer>(dimCoordList, repeat);
				for (int j = 0; j < allCoords.length; j++) {
					allCoords[j][i] = circleDimCoordList.rotate();
				}
			}
			
			// for each coords, get a sub table
			tabs = new Table[allCoords.length];
			for (int i = 0; i < allCoords.length; i++) {
				int[] subTabCoords = allCoords[i];
				Table subTab = t.getSubTable(subTabCoords);
				tabs[i] = subTab;
			}
			
			return tabs;
		}
	}
	
	/**
	 * Exports the table content to CSV format(text in byte array). If the table's dimension 
	 * is bigger than 2, it will be decomposed into several sub tables of 2 dimensions.  
	 * @param t the table to export
	 * @return a byte array of CSV representing the table 
	 * @throws TableException
	 */
	public static byte[] toCsv(Table t) throws TableException {
		return toCsvString(t, new DecimalFormatSymbols(Locale.getDefault()).getDecimalSeparator(), ';', true).getBytes();
	}
	
	/**
	 * Exports the table content to CSV format(text in byte array). If the table's dimension 
	 * is bigger than 2, it will be decomposed into several sub tables of 2 dimensions.  
	 * @param t the table to export
	 * @param withHeaders if table header should be also exported or not
	 * @return a byte array of CSV representing the table
	 * @throws TableException
	 */
	public static byte[] toCsv(Table t, boolean withHeaders) throws TableException {
		return toCsvString(t, new DecimalFormatSymbols(Locale.getDefault()).getDecimalSeparator(), ';', withHeaders).getBytes();
	}
	
	/**
	 * Converts the table content to CVS format(text in string). If the table's dimension 
	 * is bigger than 2, it will be decomposed into several sub tables of 2 dimensions.  
	 * @param t the table to convert
	 * @return a string  of CSV representing the table
	 * @throws TableException
	 */
	public static String toCsvString(Table t) throws TableException {
		return toCsvString(t, new DecimalFormatSymbols(Locale.getDefault()).getDecimalSeparator(), ';', true);
	}

	/**
	 * Converts the table content to CVS format(text in string). If the table's dimension 
	 * is bigger than 2, it will be decomposed into several sub tables of 2 dimensions.  
	 * @param t the table to convert
	 * @param withHeaders if table header should be also exported or not
	 * @return a string of CSV representing the table
	 * @throws TableException
	 */
	public static String toCsvString(Table t, boolean withHeaders) throws TableException {
		return toCsvString(t, new DecimalFormatSymbols(Locale.getDefault()).getDecimalSeparator(), ';', withHeaders);
	}

	/**
	 * Converts the table content to CVS format(text in string). If the table's dimension 
	 * is bigger than 2, it will be decomposed into several sub tables of 2 dimensions. 
	 * @param t the table
	 * @param decimalSymbol the decimal symbol to use in number
	 * @param delimiter the delimiter char to use in CSV
	 * @param withHeaders if table header should be also exported or not
	 * @return a string of CSV representing the table
	 * @throws TableException
	 */
	public static String toCsvString(Table t, char decimalSymbol, char delimiter, boolean withHeaders) throws TableException {
		StringBuilder sbCsv = new StringBuilder();
		
		int tDim = t.getDimensions().length;
		
		/*Table[] subTabs = getAll2DimSubTables(t);
		int subTabsNum = subTabs.length;*/
		Table[] subTabs = getAllSubTables(t);
		int subTabsNum = subTabs.length;
		boolean subTabCoordsHeaders = subTabsNum > 1 ? true : false;

		for (int i = 0; i < subTabsNum; i++) {
		//for (Map.Entry<List<String[]>,Table> entry : subTabs.entrySet()) {
			Table cTab = subTabs[i];
			//Table cTab = entry.getValue();
			String[][] cTabRowHeaders = tDim > 1 ? cTab.getHeaders(0) : new String[][] {{}};
			String[][] cTabColHeaders = tDim > 1 ? cTab.getHeaders(1) : cTab.getHeaders(0);
			Object[][] cTabData = tDim > 1 ? (Object[][]) cTab.getData() : new Object[][] {(Object[]) cTab.getData()};
			
			// if need to add sub table's coords title
			if (subTabCoordsHeaders) {
				Object[] coordsHdrs = t.getHeaders(cTab.getCoordinates());
				if (coordsHdrs != null) {
					for (int j = 0; j < coordsHdrs.length; j++) {
						sbCsv.append(getAxisAlias(tDim - j - 1)).append(" : ");
						
						String[] iCoordsHdrs = (String[]) coordsHdrs[j];
						for (int k = 0; k < iCoordsHdrs.length; k++) {
							sbCsv.append(iCoordsHdrs[k]);
							if (k  < iCoordsHdrs.length - 1)
								sbCsv.append(" -- ");
						}
						if (j < coordsHdrs.length - 1)
							sbCsv.append("; ");
					}
					sbCsv.append("\n");
				}
			}
			
			// generate table col headers
			if (withHeaders) {				
				int cornerWidth = cTabRowHeaders[0].length > 0 ? cTabRowHeaders.length : 0;
				for (int j = 0; j < cTabColHeaders.length; j++) {
					// corner blank padding
					for (int k = 0; k < cornerWidth; k++) {
						sbCsv.append(delimiter);
					}
					// column header
					String[] chRow = cTabColHeaders[j];
					for (int k = 0; k < chRow.length; k++) {
						sbCsv.append(chRow[k]).append(delimiter);
					}
					sbCsv.append("\n");
				}
			}
			
			// fill in data
			for (int j = 0; j < cTabData.length; j++) {
				// if row header
				if (withHeaders) {
					for (int k = 0; k < cTabRowHeaders.length; k++) {
						if (cTabRowHeaders[k].length > 0)
							sbCsv.append(cTabRowHeaders[k][j]).append(delimiter);
					}
				}
				
				// data cell
				Object[] cells = cTabData[j];
				for (int k = 0; k < cells.length; k++) {
					Object v = cells[k];
					if (v == null) {
						sbCsv.append(delimiter);
					} else if (v instanceof Double) {
						if (decimalSymbol == '.')
							sbCsv.append(v).append(delimiter);
						else
							sbCsv.append(v.toString().replace('.', decimalSymbol)).append(delimiter);
					} else {
						sbCsv.append(v).append(delimiter);
					}
				}
				sbCsv.append("\n");
			}
			
			// add multi-2dim tables separator
			sbCsv.append(";\n");
		}

		return sbCsv.toString();
	}
	
	/**
	 * Exports table content to Excel format(excel file in byte array). If the table's dimension 
	 * is bigger than 2, it will be decomposed into several sub tables of 2 dimensions.  
	 * @param t the table to export
	 * @param withHeaders if table header should be also exported or not
	 * @return a byte array of excel representing table content 
	 * @throws TableException
	 */
	public static byte[] toExcel(Table t, boolean withHeaders) throws TableException {
		byte[] binaryExcel = null;
		
		try {
			ByteArrayOutputStream bos = new ByteArrayOutputStream(); 
			
			Workbook xls = toExcelWorkbook(t, withHeaders);
			xls.write(bos);
			binaryExcel = bos.toByteArray();
			bos.flush();
			bos.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		return binaryExcel;
	}

	/**
	 * Exports table content to Excel format(excel file in byte array). If the table's dimension 
	 * is bigger than 2, it will be decomposed into several sub tables of 2 dimensions.  
	 * @param t the table to export
	 * @return a byte array of excel representing table content 
	 * @throws TableException
	 */
	public static byte[] toExcel(Table t) throws TableException {
		return toExcel(t, true);
	}
	
	/**
	 * Converts table content to Excel format(POI Workbook object). If the table's dimension 
	 * is bigger than 2, it will be decomposed into several sub tables of 2 dimensions.  
	 * @param t the table to convert
	 * @return a Workbook object of excel representing table content 
	 * @throws TableException
	 */
	public static Workbook toExcelWorkbook(Table t) throws TableException {
		return toExcelWorkbook(t, true);
	}
	
	/**
	 * Converts table content to Excel format(POI Workbook object). If the table's dimension 
	 * is bigger than 2, it will be decomposed into several sub tables of 2 dimensions.  
	 * @param t the table to convert
	 * @param withHeaders if table header should be also exported or not
	 * @return a Workbook object of excel representing table content 
	 * @throws TableException
	 */
	public static Workbook toExcelWorkbook(Table t, boolean withHeaders) throws TableException {
		Workbook wb = new HSSFWorkbook();
		
		CellStyle headerStyle = wb.createCellStyle();
		headerStyle.setBorderTop(CellStyle.BORDER_THIN);
		headerStyle.setTopBorderColor(IndexedColors.BLACK.getIndex());
		headerStyle.setBorderBottom(CellStyle.BORDER_THIN);
		headerStyle.setBottomBorderColor(IndexedColors.BLACK.getIndex());
		headerStyle.setBorderLeft(CellStyle.BORDER_THIN);
		headerStyle.setLeftBorderColor(IndexedColors.BLACK.getIndex());
		headerStyle.setBorderRight(CellStyle.BORDER_THIN);
		headerStyle.setRightBorderColor(IndexedColors.BLACK.getIndex());
		headerStyle.setAlignment(CellStyle.ALIGN_CENTER);
		headerStyle.setVerticalAlignment(CellStyle.VERTICAL_CENTER);
		
		CellStyle titleStyle = wb.createCellStyle();
		Font titleFont = wb.createFont();
		titleFont.setBoldweight(Font.BOLDWEIGHT_BOLD);
		titleStyle.setFont(titleFont);
		
		int tDim = t.getDimensions().length;
		/*Table[] subTabs = getAll2DimSubTables(t);
		int subTabsNum = subTabs.length;*/
		Table[] subTabs = getAllSubTables(t);
		int subTabsNum = subTabs.length;
		boolean subTabCoordsHeaders = subTabsNum > 1 ? true : false;

		for (int i = 0; i < subTabsNum; i++) {
			Table cTab = subTabs[i];
			String[][] cTabRowHeaders = tDim > 1 ? cTab.getHeaders(0) : new String[][] {{}};
			String[][] cTabColHeaders = tDim > 1 ? cTab.getHeaders(1) : cTab.getHeaders(0);
			Object[][] cTabData = tDim > 1 ? (Object[][]) cTab.getData() : new Object[][] {(Object[]) cTab.getData()};
			
			Sheet sheet = wb.createSheet();

			List<int[]> mergeZoneCoords = new ArrayList<int[]>();
			
			// column header
			int rowCur = 0;
			int columnCur = 0;
			
			// if need to add sub table's coords title
			if (subTabCoordsHeaders) {
				StringBuilder sb = new StringBuilder();
				Object[] coordsHdrs = t.getHeaders(cTab.getCoordinates());
				for (int j = 0; j < coordsHdrs.length; j++) {
					//sb.append("Axis(").append(tDim - j).append(") : ");
					sb.append(getAxisAlias(tDim - j - 1)).append(" : ");
					
					String[] iCoordsHdrs = (String[]) coordsHdrs[j];
					for (int k = 0; k < iCoordsHdrs.length; k++) {
						sb.append(iCoordsHdrs[k]);
						if (k  < iCoordsHdrs.length - 1)
							sb.append(" -- ");
					}
					if (j < coordsHdrs.length - 1)
						sb.append("; ");
				}
				
				Row row = sheet.createRow(rowCur);
				Cell c = row.createCell(0);
				c.setCellStyle(titleStyle);
				c.setCellValue(sb.toString());
				
				int tabWidth = (withHeaders ? cTabRowHeaders.length : 0) + cTabData[0].length;
				sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, tabWidth - 1));
				rowCur++;
				
				// create blank row
				sheet.createRow(rowCur);
				rowCur++;
			}
			
			if (withHeaders) {
				for (int j = 0; j < cTabColHeaders.length; j++) {
					columnCur = 0;
					
					String[] chRow = cTabColHeaders[j];
					Row row = sheet.createRow(rowCur); 
					
					// corner (blank cell)
					for (int k = 0; k < cTabRowHeaders.length; k++) {
						if (cTabRowHeaders[k].length > 0) {
							row.createCell(k).setCellStyle(headerStyle);
							columnCur++;
						}
					}
					
					// column header
					boolean mergeFlag = false;
					for (int k = 0; k < chRow.length; k++) {
						Cell c = row.createCell(k + columnCur);
						c.setCellValue(chRow[k]);
						c.setCellStyle(headerStyle);
						
						// if to be merged
						if (k < chRow.length - 1 && chRow[k].equals(chRow[k + 1]) && 
								(k == 0 || !chRow[k].equals(chRow[k - 1])) ) {
							int[] mergeCoords = new int[] {rowCur, rowCur, k + columnCur, k + columnCur};
							mergeZoneCoords.add(mergeCoords);
							mergeFlag = true;
						} else {
							if (mergeFlag)
								mergeZoneCoords.get(mergeZoneCoords.size() - 1)[3] = k + columnCur;
							if (k == chRow.length - 1 || !chRow[k].equals(chRow[k + 1]))
								mergeFlag = false;
						}
					}
					rowCur++;
				}
				
				columnCur = 0;
				for (int j = 0; j < cTabRowHeaders.length; j++) {
					String[] rhRow = cTabRowHeaders[j];
					
					boolean mergeFlag = false;
					for (int k = 0; k < rhRow.length; k++) {
						// if to be merged
						if (k < rhRow.length - 1 && rhRow[k].equals(rhRow[k + 1]) && 
								(k == 0 || !rhRow[k].equals(rhRow[k - 1])) ) {
							int[] mergeCoords = new int[] {rowCur + k, rowCur + k, j + columnCur, j + columnCur};
							mergeZoneCoords.add(mergeCoords);
							mergeFlag = true;
						} else {
							if (mergeFlag)
								mergeZoneCoords.get(mergeZoneCoords.size() - 1)[1] = rowCur + k;
							if (k == rhRow.length - 1 || !rhRow[k].equals(rhRow[k + 1]))
								mergeFlag = false;
						}
					}
				}
			}
			
			// data
			for (int j = 0; j < cTabData.length; j++) {
				columnCur = 0;
				Row row = sheet.createRow(rowCur); 
				
				// row header
				if (withHeaders) {
					for (int k = 0; k < cTabRowHeaders.length; k++) {
						if (cTabRowHeaders[k].length > 0) {
							Cell c = row.createCell(k);
							c.setCellValue(cTabRowHeaders[k][j]);
							c.setCellStyle(headerStyle);
							
							columnCur++;
						}
					}
				}
				
				// data
				Object[] cells = cTabData[j];
				for (int k = 0; k < cells.length; k++) {
					Object v = cells[k];
					if (v == null) {
						row.createCell(k + columnCur);
					} else if (v instanceof Double) {
						row.createCell(k + columnCur).setCellValue(((Double) v).doubleValue());
					} else {
						row.createCell(k + columnCur).setCellValue(v.toString());
					}
				}
				rowCur++;
			}
			
			// auto width
			int leftPadding = cTabRowHeaders.length;
			int topPadding = cTabColHeaders.length;
			
			for (int j = 0; j < leftPadding + cTabColHeaders[0].length; j++) {
				sheet.autoSizeColumn(j);
			}
			
			// merge corner
			sheet.addMergedRegion(new CellRangeAddress(
					subTabCoordsHeaders ? 2 : 0, 
					topPadding - 1 + (subTabCoordsHeaders ? 2 : 0), 
					0, 
					leftPadding - 1));

			for (int j = 0; j < mergeZoneCoords.size(); j++) {
				int[] mergeCoords = mergeZoneCoords.get(j);
				sheet.addMergedRegion(new CellRangeAddress(mergeCoords[0],mergeCoords[1],mergeCoords[2],mergeCoords[3]));
			}
		}
		
		return wb;
	}
	
	/**
	 * Converts the table to a string which can be evaluated to a Javascript multidimensional array.
	 * Eg. for a table of [2, 2, 3], it's Javascript array string may be like : <br> 
	 * <code><pre>
	 * { 
	 *   dimensions : [2, 2, 4], 
	 *   headers : [
	 *   	[Internet, Reseller], 
	 *   	[[Europe, Europe], [France, Germany]], 
	 *   	[2006, 2007, 2008, 2009],
	 *   ],
	 *   data : [
	 *   	[[124, 240, 90, 176], [60, 200, 134,79]],
	 *   	[[, 240, 88, 139], [82, 201, 145,'error']]
	 *   ],
	 *   precision : 2,
	 *   coordinates : []
	 * }
	 * </pre></code>
	 * @param t the table to convert
	 * @return a byte array representing table content 
	 */
	public static String toJsArrayString(Table t) {
		return toJsArrayString(t, true);
	}
	
	/**
	 * Converts the table to a string which can be evaluated to a Javascript multidimensional array.
	 * Eg. for a table of [2, 2, 3], it's Javascript array string may be like : <br> 
	 * <code><pre>
	 * { 
	 *   dimensions : [2, 2, 4], 
	 *   data : [
	 *   	[[124, 240, 90, 176], [60, 200, 134,79]],
	 *   	[[, 240, 88, 139], [82, 201, 145,'error']]
	 *   ],
	 *   precision : 2,
	 *   coordinates : []
	 * }
	 * </pre></code>
	 * @param t the table to convert
	 * @return a byte array representing table content 
	 */
	public static String toJsArrayString(Table t, boolean withHeaders) {
		StringBuilder sb = new StringBuilder("{ ");
		
		// dimensions :
		sb.append("dimensions : ").append(Arrays.toString(t.getDimensions())).append(", ");
		
		// headers :
		Object[] tHdrs = t.getHeaders();
		for (int i = 0; i < tHdrs.length; i++) {
			String[][] dimHdrs = (String[][]) tHdrs[i];
			for (int j = 0; j < dimHdrs.length; j++) {
				String[] dimHdr = dimHdrs[j];
				for (int k = 0; k < dimHdr.length; k++) {
					dimHdr[k] = "'" + dimHdr[k] + "'";
				}
			}
		}
		sb.append("headers : ").append(Arrays.deepToString(tHdrs)).append(", ");
		
		// data
		sb.append("data : ").append(Arrays.deepToString((Object[])t.getData())).append(", ");
		
		// precision
		sb.append("precision : ").append(t.getPrecision()).append(", ");
		
		// coordinates
		sb.append("coordinates : ").append(t.getCoordinates() == null ? "[]" : Arrays.toString(t.getCoordinates()));
		
		sb.append(" }");
		return sb.toString();
	}
	
	/**
	 * Exports table to XML document (in byte array of string).
	 * Eg. for a table of [2, 2, 3] sliced from its parent table [3, 2, 2, 3] at the coordinates[1], 
	 * its XML form may be like : <br> 
	 * <pre><code>
	 * &lt;table&gt;
	 *    &lt;dimensions&gt;
	 *       &lt;element axis="2"&gt;2&lt;/element&gt;
	 *       &lt;element axis="1"&gt;2&lt;/element&gt;
	 *       &lt;element axis="0"&gt;4&lt;/element&gt;
	 *    &lt;/dimensions&gt;
	 *    &lt;headers&gt;
	 *       &lt;element axis="2"&gt;
	 *          &lt;element&gt;
	 *             &lt;element&gt;Internet&lt;/element&gt;
	 *             &lt;element&gt;Reseller&lt;/element&gt;
	 *          &lt;/element&gt;
	 *       &lt;/element&gt;
	 *       &lt;element axis="1"&gt;
	 *          &lt;element&gt;
	 *             &lt;element&gt;Europe&lt;/element&gt;
	 *             &lt;element&gt;Europe&lt;/element&gt;
	 *          &lt;/element&gt;
	 *          &lt;element&gt;
	 *             &lt;element&gt;France&lt;/element&gt;
	 *             &lt;element&gt;Germany&lt;/element&gt;
	 *          &lt;/element&gt;
	 *       &lt;/element&gt;
	 *       &lt;element axis="0"&gt;
	 *          &lt;element&gt;
	 *             &lt;element&gt;2006&lt;/element&gt;
	 *             &lt;element&gt;2007&lt;/element&gt;
	 *             &lt;element&gt;2008&lt;/element&gt;
	 *             &lt;element&gt;2009&lt;/element&gt;
	 *          &lt;/element&gt;
	 *       &lt;/element&gt;
	 *    &lt;/headers&gt;
	 *    &lt;data&gt;
	 *       &lt;element axis="3"&gt;
	 *          &lt;element axis="2"&gt;
	 *             &lt;element axis="1"&gt;
	 *                &lt;element&gt;
	 *                   &lt;element&gt;124&lt;/element&gt;
	 *                   &lt;element&gt;240&lt;/element&gt;
	 *                   &lt;element&gt;90&lt;/element&gt;
	 *                   &lt;element&gt;176&lt;/element&gt;
	 *                &lt;/element&gt;
	 *                &lt;element&gt;
	 *                   &lt;element&gt;60&lt;/element&gt;
	 *                   &lt;element&gt;200&lt;/element&gt;
	 *                   &lt;element&gt;134&lt;/element&gt;
	 *                   &lt;element&gt;79&lt;/element&gt;
	 *                &lt;/element&gt;
	 *             &lt;/element&gt;
	 *          &lt;/element&gt;
	 *          &lt;element axis="2"&gt;
	 *             &lt;element axis="1"&gt;
	 *                &lt;element&gt;
	 *                   &lt;element&gt;&lt;/element&gt;
	 *                   &lt;element&gt;240&lt;/element&gt;
	 *                   &lt;element&gt;88&lt;/element&gt;
	 *                   &lt;element&gt;139&lt;/element&gt;
	 *                &lt;/element&gt;
	 *                &lt;element&gt;
	 *                   &lt;element&gt;82&lt;/element&gt;
	 *                   &lt;element&gt;201&lt;/element&gt;
	 *                   &lt;element&gt;145&lt;/element&gt;
	 *                   &lt;element&gt;error&lt;/element&gt;
	 *                &lt;/element&gt;
	 *             &lt;/element&gt;
	 *          &lt;/element&gt;
	 *       &lt;/element&gt;
	 *    &lt;/data&gt;
	 *    &lt;precision&gt;
	 *       &lt;element&gt;0&lt;/element&gt;
	 *    &lt;/precision&gt;
	 *    &lt;coordinates&gt;
	 *       &lt;element axis="3"&gt;1&lt;/element&gt;
	 *    &lt;/coordinates&gt;
	 * &lt;/table&gt;
	 * </code></pre>
	 * @param t the table to convert
	 * @return a byte array of XML string representing table content
	 */
	public static byte[] toXml(Table t) {
		return toXmlDocument(t, true).asXML().getBytes();
	}
	
	/**
	 * Exports the table to XML document (in byte array of string).
	 * Eg. for a table of [2, 2, 3] sliced from its parent table [3, 2, 2, 3] at the coordinates[1], 
	 * its XML form may be like : <br> 
	 * <pre><code>
	 * &lt;table&gt;
	 *    &lt;dimensions&gt;
	 *       &lt;element axis="2"&gt;2&lt;/element&gt;
	 *       &lt;element axis="1"&gt;2&lt;/element&gt;
	 *       &lt;element axis="0"&gt;4&lt;/element&gt;
	 *    &lt;/dimensions&gt;
	 *    &lt;headers&gt;
	 *       &lt;element axis="2"&gt;
	 *          &lt;element&gt;
	 *             &lt;element&gt;Internet&lt;/element&gt;
	 *             &lt;element&gt;Reseller&lt;/element&gt;
	 *          &lt;/element&gt;
	 *       &lt;/element&gt;
	 *       &lt;element axis="1"&gt;
	 *          &lt;element&gt;
	 *             &lt;element&gt;Europe&lt;/element&gt;
	 *             &lt;element&gt;Europe&lt;/element&gt;
	 *          &lt;/element&gt;
	 *          &lt;element&gt;
	 *             &lt;element&gt;France&lt;/element&gt;
	 *             &lt;element&gt;Germany&lt;/element&gt;
	 *          &lt;/element&gt;
	 *       &lt;/element&gt;
	 *       &lt;element axis="0"&gt;
	 *          &lt;element&gt;
	 *             &lt;element&gt;2006&lt;/element&gt;
	 *             &lt;element&gt;2007&lt;/element&gt;
	 *             &lt;element&gt;2008&lt;/element&gt;
	 *             &lt;element&gt;2009&lt;/element&gt;
	 *          &lt;/element&gt;
	 *       &lt;/element&gt;
	 *    &lt;/headers&gt;
	 *    &lt;data&gt;
	 *       &lt;element axis="3"&gt;
	 *          &lt;element axis="2"&gt;
	 *             &lt;element axis="1"&gt;
	 *                &lt;element&gt;
	 *                   &lt;element&gt;124&lt;/element&gt;
	 *                   &lt;element&gt;240&lt;/element&gt;
	 *                   &lt;element&gt;90&lt;/element&gt;
	 *                   &lt;element&gt;176&lt;/element&gt;
	 *                &lt;/element&gt;
	 *                &lt;element&gt;
	 *                   &lt;element&gt;60&lt;/element&gt;
	 *                   &lt;element&gt;200&lt;/element&gt;
	 *                   &lt;element&gt;134&lt;/element&gt;
	 *                   &lt;element&gt;79&lt;/element&gt;
	 *                &lt;/element&gt;
	 *             &lt;/element&gt;
	 *          &lt;/element&gt;
	 *          &lt;element axis="2"&gt;
	 *             &lt;element axis="1"&gt;
	 *                &lt;element&gt;
	 *                   &lt;element&gt;&lt;/element&gt;
	 *                   &lt;element&gt;240&lt;/element&gt;
	 *                   &lt;element&gt;88&lt;/element&gt;
	 *                   &lt;element&gt;139&lt;/element&gt;
	 *                &lt;/element&gt;
	 *                &lt;element&gt;
	 *                   &lt;element&gt;82&lt;/element&gt;
	 *                   &lt;element&gt;201&lt;/element&gt;
	 *                   &lt;element&gt;145&lt;/element&gt;
	 *                   &lt;element&gt;error&lt;/element&gt;
	 *                &lt;/element&gt;
	 *             &lt;/element&gt;
	 *          &lt;/element&gt;
	 *       &lt;/element&gt;
	 *    &lt;/data&gt;
	 *    &lt;precision&gt;
	 *       &lt;element&gt;0&lt;/element&gt;
	 *    &lt;/precision&gt;
	 *    &lt;coordinates&gt;
	 *       &lt;element axis="3"&gt;1&lt;/element&gt;
	 *    &lt;/coordinates&gt;
	 * &lt;/table&gt;
	 * </code></pre>
	 * @param t the table to Export
	 * @param withHeaders if table header should be also exported or not
	 * @return a byte array of XML string representing table content
	 */
	public static byte[] toXml(Table t, boolean withHeaders) {
		return toXmlDocument(t, withHeaders).asXML().getBytes();
	}
	
	/**
	 * Converts the table to a dom4j XML Document object.
	 * Eg. for a table of [2, 2, 3] sliced from its parent table [3, 2, 2, 3] at the coordinates[1], 
	 * its XML form may be like : <br> 
	 * <pre><code>
	 * &lt;table&gt;
	 *    &lt;dimensions&gt;
	 *       &lt;element axis="2"&gt;2&lt;/element&gt;
	 *       &lt;element axis="1"&gt;2&lt;/element&gt;
	 *       &lt;element axis="0"&gt;4&lt;/element&gt;
	 *    &lt;/dimensions&gt;
	 *    &lt;headers&gt;
	 *       &lt;element axis="2"&gt;
	 *          &lt;element&gt;
	 *             &lt;element&gt;Internet&lt;/element&gt;
	 *             &lt;element&gt;Reseller&lt;/element&gt;
	 *          &lt;/element&gt;
	 *       &lt;/element&gt;
	 *       &lt;element axis="1"&gt;
	 *          &lt;element&gt;
	 *             &lt;element&gt;Europe&lt;/element&gt;
	 *             &lt;element&gt;Europe&lt;/element&gt;
	 *          &lt;/element&gt;
	 *          &lt;element&gt;
	 *             &lt;element&gt;France&lt;/element&gt;
	 *             &lt;element&gt;Germany&lt;/element&gt;
	 *          &lt;/element&gt;
	 *       &lt;/element&gt;
	 *       &lt;element axis="0"&gt;
	 *          &lt;element&gt;
	 *             &lt;element&gt;2006&lt;/element&gt;
	 *             &lt;element&gt;2007&lt;/element&gt;
	 *             &lt;element&gt;2008&lt;/element&gt;
	 *             &lt;element&gt;2009&lt;/element&gt;
	 *          &lt;/element&gt;
	 *       &lt;/element&gt;
	 *    &lt;/headers&gt;
	 *    &lt;data&gt;
	 *       &lt;element axis="3"&gt;
	 *          &lt;element axis="2"&gt;
	 *             &lt;element axis="1"&gt;
	 *                &lt;element&gt;
	 *                   &lt;element&gt;124&lt;/element&gt;
	 *                   &lt;element&gt;240&lt;/element&gt;
	 *                   &lt;element&gt;90&lt;/element&gt;
	 *                   &lt;element&gt;176&lt;/element&gt;
	 *                &lt;/element&gt;
	 *                &lt;element&gt;
	 *                   &lt;element&gt;60&lt;/element&gt;
	 *                   &lt;element&gt;200&lt;/element&gt;
	 *                   &lt;element&gt;134&lt;/element&gt;
	 *                   &lt;element&gt;79&lt;/element&gt;
	 *                &lt;/element&gt;
	 *             &lt;/element&gt;
	 *          &lt;/element&gt;
	 *          &lt;element axis="2"&gt;
	 *             &lt;element axis="1"&gt;
	 *                &lt;element&gt;
	 *                   &lt;element&gt;&lt;/element&gt;
	 *                   &lt;element&gt;240&lt;/element&gt;
	 *                   &lt;element&gt;88&lt;/element&gt;
	 *                   &lt;element&gt;139&lt;/element&gt;
	 *                &lt;/element&gt;
	 *                &lt;element&gt;
	 *                   &lt;element&gt;82&lt;/element&gt;
	 *                   &lt;element&gt;201&lt;/element&gt;
	 *                   &lt;element&gt;145&lt;/element&gt;
	 *                   &lt;element&gt;error&lt;/element&gt;
	 *                &lt;/element&gt;
	 *             &lt;/element&gt;
	 *          &lt;/element&gt;
	 *       &lt;/element&gt;
	 *    &lt;/data&gt;
	 *    &lt;precision&gt;
	 *       &lt;element&gt;0&lt;/element&gt;
	 *    &lt;/precision&gt;
	 *    &lt;coordinates&gt;
	 *       &lt;element axis="3"&gt;1&lt;/element&gt;
	 *    &lt;/coordinates&gt;
	 * &lt;/table&gt;
	 * </code></pre>
	 * @param t the table to convert
	 * @return a dom4j Document object representing table content 
	 */
	public static Document toXmlDocument(Table t) {
		return toXmlDocument(t, true);
	}

	/**
	 * Converts the table to a dom4j XML Document object.
	 * Eg. for a table of [2, 2, 3] sliced from its parent table [3, 2, 2, 3] at the coordinates[1], 
	 * its XML form may be like : <br> 
	 * <pre><code>
	 * &lt;table&gt;
	 *    &lt;dimensions&gt;
	 *       &lt;element axis="2"&gt;2&lt;/element&gt;
	 *       &lt;element axis="1"&gt;2&lt;/element&gt;
	 *       &lt;element axis="0"&gt;4&lt;/element&gt;
	 *    &lt;/dimensions&gt;
	 *    &lt;headers&gt;
	 *       &lt;element axis="2"&gt;
	 *          &lt;element&gt;
	 *             &lt;element&gt;Internet&lt;/element&gt;
	 *             &lt;element&gt;Reseller&lt;/element&gt;
	 *          &lt;/element&gt;
	 *       &lt;/element&gt;
	 *       &lt;element axis="1"&gt;
	 *          &lt;element&gt;
	 *             &lt;element&gt;Europe&lt;/element&gt;
	 *             &lt;element&gt;Europe&lt;/element&gt;
	 *          &lt;/element&gt;
	 *          &lt;element&gt;
	 *             &lt;element&gt;France&lt;/element&gt;
	 *             &lt;element&gt;Germany&lt;/element&gt;
	 *          &lt;/element&gt;
	 *       &lt;/element&gt;
	 *       &lt;element axis="0"&gt;
	 *          &lt;element&gt;
	 *             &lt;element&gt;2006&lt;/element&gt;
	 *             &lt;element&gt;2007&lt;/element&gt;
	 *             &lt;element&gt;2008&lt;/element&gt;
	 *             &lt;element&gt;2009&lt;/element&gt;
	 *          &lt;/element&gt;
	 *       &lt;/element&gt;
	 *    &lt;/headers&gt;
	 *    &lt;data&gt;
	 *       &lt;element axis="3"&gt;
	 *          &lt;element axis="2"&gt;
	 *             &lt;element axis="1"&gt;
	 *                &lt;element&gt;
	 *                   &lt;element&gt;124&lt;/element&gt;
	 *                   &lt;element&gt;240&lt;/element&gt;
	 *                   &lt;element&gt;90&lt;/element&gt;
	 *                   &lt;element&gt;176&lt;/element&gt;
	 *                &lt;/element&gt;
	 *                &lt;element&gt;
	 *                   &lt;element&gt;60&lt;/element&gt;
	 *                   &lt;element&gt;200&lt;/element&gt;
	 *                   &lt;element&gt;134&lt;/element&gt;
	 *                   &lt;element&gt;79&lt;/element&gt;
	 *                &lt;/element&gt;
	 *             &lt;/element&gt;
	 *          &lt;/element&gt;
	 *          &lt;element axis="2"&gt;
	 *             &lt;element axis="1"&gt;
	 *                &lt;element&gt;
	 *                   &lt;element&gt;&lt;/element&gt;
	 *                   &lt;element&gt;240&lt;/element&gt;
	 *                   &lt;element&gt;88&lt;/element&gt;
	 *                   &lt;element&gt;139&lt;/element&gt;
	 *                &lt;/element&gt;
	 *                &lt;element&gt;
	 *                   &lt;element&gt;82&lt;/element&gt;
	 *                   &lt;element&gt;201&lt;/element&gt;
	 *                   &lt;element&gt;145&lt;/element&gt;
	 *                   &lt;element&gt;error&lt;/element&gt;
	 *                &lt;/element&gt;
	 *             &lt;/element&gt;
	 *          &lt;/element&gt;
	 *       &lt;/element&gt;
	 *    &lt;/data&gt;
	 *    &lt;precision&gt;
	 *       &lt;element&gt;0&lt;/element&gt;
	 *    &lt;/precision&gt;
	 *    &lt;coordinates&gt;
	 *       &lt;element axis="3"&gt;1&lt;/element&gt;
	 *    &lt;/coordinates&gt;
	 * &lt;/table&gt;
	 * </code></pre>
	 * @param t the table to convert
	 * @param withHeaders if table header should be also exported or not
	 * @return a dom4j Document object representing table content 
	 */
	public static Document toXmlDocument(Table t, boolean withHeaders) {
		Document document = DocumentHelper.createDocument();
		
		Element rootElm = document.addElement("table");
		Element dimensionsElm = rootElm.addElement("dimensions");
		Element headersElm = rootElm.addElement("headers");
		Element dataElm = rootElm.addElement("data");
		Element precisionElm = rootElm.addElement("precision");
		Element coordinatesElm = rootElm.addElement("coordinates");
		
		String commonElmName = "element";
		String axisAttribute = "axis";
		
		// load dimensions
		int[] tDims = t.getDimensions();
		for (int i = 0; i < tDims.length; i++) {
			Element e = dimensionsElm.addElement(commonElmName);
			e.addAttribute(axisAttribute, Integer.toString(tDims.length - i - 1));
			e.setText(Integer.toString(tDims[i]));
		}
		
		// load headers
		Object[] tHdrs = t.getHeaders();
		for (int i = 0; i < tHdrs.length; i++) {
			String[][] tDimHdrs = (String[][]) tHdrs[i];
			Element e0 = headersElm.addElement(commonElmName);
			e0.addAttribute(axisAttribute, Integer.toString(tHdrs.length - i - 1));
			
			for (int j = 0; j < tDimHdrs.length; j++) {
				String[] tDimHdr = tDimHdrs[j];
				Element e1 = e0.addElement(commonElmName);
				for (int k = 0; k < tDimHdr.length; k++) {
					e1.addElement(commonElmName).setText(tDimHdr[k]);
				}
			}
		}
		
		// load data
		Object tData = t.getData();
		buildRecursiveDataXml(dataElm, tData, commonElmName, axisAttribute);
		
		// load precision
		precisionElm.addElement(commonElmName).setText(Integer.toString(t.getPrecision()));
		
		// load coordiantes
		int[] coords = t.getCoordinates();
		if (coords == null) {
			coordinatesElm.addElement(commonElmName);
		} else {
			for (int i  = 0; i < coords.length; i++) {
				Element coordElm = coordinatesElm.addElement(commonElmName);
				coordElm.addAttribute(axisAttribute, Integer.toString(tDims.length - 1 - i));
			}
		}
		
		return document;
	}
	
	private static void buildRecursiveDataXml(Element p, Object d, String e, String a) {	
		// calculate array depth == axis
		int axis = 0;
		Object array = d;
		while (Array.get(array, 0) instanceof Object[]) {
			array = Array.get(array, 0);
			axis++;
		}
		
		if (axis > 0) {
			Element elm = p.addElement(e);
			elm.addAttribute(a, Integer.toString(axis));
			
			int dLength = Array.getLength(d);
			for (int i = 0; i < dLength; i++) {
				buildRecursiveDataXml(elm, Array.get(d, i), e, a);
			}
		} else if (axis == 0) {
			Element elm = p.addElement(e);
			elm.addAttribute(a, Integer.toString(0));
			Element e0 = elm.addElement(e);
			
			Object[] tData = (Object[]) d;
			for (int i = 0; i < tData.length; i++) {
				Element e1 = e0.addElement(e);
				if (tData[i] != null)
					e1.setText(tData[i].toString());
			}
		}
	}
}